#include "stdafx.h"
#include "Font.h"

#include "GlyphBitmap.h"


static const uint8 TAB_SIZE = 2;
static uint COLOR_IDS[] =
{
	Color::Black,		//	0
	Color::White,		//	1
	Color::Grey,		//	2
	Color::Red,			//	3
	Color::Green,		//	4
	Color::Blue,		//	5
	Color::Magenta,	//	6
	Color::Yellow,	//	7
	Color::Cyan,		//	8
	Color::Orange		//	9
};


CFont::CFont(const char* name, uint size)
: m_FTLib(0)
, m_FTFace(0)
, m_size(size)
, m_name(name)
, m_kerning(false)
, m_texture(0)
, m_symbols_buffered(0)
{
	assert(name);
	assert(size);

	memset(m_glyphs, 0, sizeof(m_glyphs));

	m_vertices = g_pRenderAPI->CreateVertexBuffer(VERTEX_BUFFER_SIZE, true);
	m_indices = g_pRenderAPI->CreateIndexBuffer(INDEX_BUFFER_SIZE, false);

	SetupIndexBuffer();
}

CFont::~CFont()
{
	if (g_pTextureManager)
		g_pTextureManager->DeleteTexture(m_texture);

	DestroyGlyphs();
	FT_Done_Face(m_FTFace);
	FT_Done_FreeType(m_FTLib);

	delete m_vertices;
	delete m_indices;
}

bool CFont::Init()
{
	assert(!m_FTLib && !m_FTFace);

	//	init freetype
	if (FT_Init_FreeType(&m_FTLib))
		return false;

	//	load font face
	string filepath = string(FONTS_DIR) + m_name;
	FT_New_Face(m_FTLib, filepath.c_str(), 0, &m_FTFace);
	FT_Set_Char_Size(m_FTFace, m_size << 6, m_size << 6, SCREEN_DPI, SCREEN_DPI);

	if (!FT_IS_SCALABLE(m_FTFace))
	{
		Error("[CFont] Non-scalable fonts not supported");
		return false;
	}

	//	load glyps and render glyph bitmaps
	LoadGlyps();

	//	calculate cache texture size
	bool calculated = false;
	uint tex_width = 128, tex_height = 128;
	while (!calculated)
	{
		uint x = TEXTURE_OFFSET, y = TEXTURE_OFFSET, next_y = TEXTURE_OFFSET;
		for (uint glyph_index = FIRST_GLYPH; glyph_index < GLYPHS_COUNT; ++glyph_index)
		{
			SGlyphSlot& slot = m_slots[glyph_index];

			//	calculate texture coordinates
			slot.pos[0] = Vec2i(x, y);
			slot.pos[1] = Vec2i(x + slot.bbox.width, y + slot.bbox.height);

			//	check if glyphs fit into texture height
			if (static_cast<uint>(slot.pos[1].y) >= tex_height)
				break;

			//	calculate next position
			x = slot.pos[1].x + TEXTURE_OFFSET;
			next_y = max(next_y, slot.pos[1].y + TEXTURE_OFFSET);

			//	check if glyphs fit into texture width
			if (x >= tex_width)
			{
				x = TEXTURE_OFFSET;
				y = next_y;
				glyph_index--;
			}

			if (glyph_index == GLYPHS_COUNT - 1)
				calculated = true;
		}

		if (!calculated)
		{
			if (tex_width <= tex_height)
				tex_width = next_power_of_2(tex_width);
			else
				tex_height = next_power_of_2(tex_height);
		}
	}
	if (tex_width > MAX_TEXTURE_SIZE || tex_height > MAX_TEXTURE_SIZE)
	{
		Error("[CFont] Font texture exceded maximum size");
		tex_width = min(tex_width, MAX_TEXTURE_SIZE);
		tex_height = min(tex_height, MAX_TEXTURE_SIZE);
	}

	//	cache glyphs bitmaps to texture
	GenerateTexture(tex_width, tex_height);

	//	save misc info
	m_ascender = FT_MulFix(m_FTFace->ascender, m_FTFace->size->metrics.y_scale) >> 6;
	m_line_spacing = FT_MulFix(m_FTFace->height, m_FTFace->size->metrics.y_scale) >> 6;
	m_kerning = FT_HAS_KERNING(m_FTFace) != 0;

	return true;
}

void CFont::RenderText(const char* text, float x, float y, const Color& color)
{
	assert(text);

	if (!*text)	//	skip empty strings
		return;

	uint r_width = gEnv->pRender->GetWidth();
	uint r_height = gEnv->pRender->GetHeight();

	//	render text
	uint index = 0;
	float cursor_pos = x;
	float baseline = y + m_ascender;
	char cur_symbol = *text;
	Color cur_color = color;
	while (cur_symbol)
	{
		if (m_symbols_buffered == STRING_BUFFER_SIZE)	//	flush if no empty place in the buffer
			Flush();

		if (cur_symbol == '\n')	//	process line ending
		{
			baseline += m_line_spacing;
			cursor_pos = x;
			index++;
		}
		else if (cur_symbol == ' ')	//	process space symbol
		{
			const SGlyphSlot& space_slot = m_slots[SPACE_INDEX];
			cursor_pos += static_cast<float>(space_slot.advance.x);
			index++;
		}
		else if (cur_symbol == '\t')	//	process tab symbol
		{
			const SGlyphSlot& space_slot = m_slots[SPACE_INDEX];
			cursor_pos += static_cast<float>(space_slot.advance.x * TAB_SIZE);
			index++;
		}
		else if (text[index] == '$' && string_utils::is_digit(text[index + 1]))	//	process text color
		{
			char color_text[2] = {text[index + 1], '\0'};
			uint color_index = 0;
			if (string_utils::from_string(color_text, color_index))
			{
				cur_color = COLOR_IDS[color_index];
				index += 2;
			}
		}
		else	//	process renderable symbol
		{
			const SGlyphSlot& slot = m_slots[cur_symbol];

			//	kerning
			float kerning_x = 0, kerning_y = 0;
			if (m_kerning && index != 0)
			{
				uint left_glyph = FT_Get_Char_Index(m_FTFace, text[index - 1]);
				uint right_glyph = FT_Get_Char_Index(m_FTFace, text[index]);

				FT_Vector ft_kerning;
				FT_Get_Kerning(m_FTFace, left_glyph, right_glyph, FT_KERNING_DEFAULT, &ft_kerning);
				kerning_x = static_cast<float>(ft_kerning.x >> 6);
				kerning_y = static_cast<float>(ft_kerning.y >> 6);
			}

			//	calculate symbol position
			S2DVertex* vb = &m_vbuffer[m_symbols_buffered*4];
			float left = cursor_pos + slot.bearing.x + kerning_x;
			float top = baseline - slot.bearing.y + kerning_y;
			float right = left + slot.bbox.width;
			float bottom = top + slot.bbox.height;

			// convert to clip space
			left = (2*left/r_width) - 1;
			top = 1 - (2*top/r_height);
			right = (2*right/r_width) - 1;
			bottom = 1 - (2*bottom/r_height);

			//	left-bottom
			vb[0].pos.x = left;
			vb[0].pos.y = bottom;
			vb[0].uv.x = slot.uv[0].x;
			vb[0].uv.y = slot.uv[1].y;
			vb[0].color = cur_color;

			//	left-top
			vb[1].pos.x = left;
			vb[1].pos.y = top;
			vb[1].uv.x = slot.uv[0].x;
			vb[1].uv.y = slot.uv[0].y;
			vb[1].color = cur_color;

			//	right-top
			vb[2].pos.x = right;
			vb[2].pos.y = top;
			vb[2].uv.x = slot.uv[1].x;
			vb[2].uv.y = slot.uv[0].y;
			vb[2].color = cur_color;

			//	right-bottom
			vb[3].pos.x = right;
			vb[3].pos.y = bottom;
			vb[3].uv.x = slot.uv[1].x;
			vb[3].uv.y = slot.uv[1].y;
			vb[3].color = cur_color;

			cursor_pos += slot.advance.x;
			index++;
			m_symbols_buffered++;
		}
		cur_symbol = text[index];
	}
}

Vec2 CFont::MeasureString(const char* text) const
{
	Vec2 size;
	while (*text)
	{
		const SGlyphSlot& slot = m_slots[*text];
		size.x += slot.advance.x;
		size.y = max(size.y, slot.bbox.height);

		text++;
	}
	return size;
}

uint CFont::GetLineSpacing()
{
	return m_line_spacing;
}

void CFont::Flush()
{
	PROFILE_FUNCTION();

	if (m_symbols_buffered != 0)
	{
		//	fill vertex buffer
		uint bytes_to_copy = m_symbols_buffered*BYTES_PER_SYMBOL;
		void* vb = m_vertices->Lock(0, 0, eVBLock_Discard);
		memcpy_s(vb, bytes_to_copy, m_vbuffer, bytes_to_copy);
		m_vertices->Unlock();

		//	render
		gEnv->pRender->SetShader("font");
		gEnv->pRender->SetTexture(m_texture);
		gEnv->pRender->DrawPrimitives(ePrimType_TriangleList, m_vertices, m_symbols_buffered * 4,
			m_indices, m_symbols_buffered * 6);
		gEnv->pRender->SetShader(0);

		//	reset buffer offset
		m_symbols_buffered = 0;
	}
}

void CFont::SetupIndexBuffer()
{
	void* ib_void = m_indices->Lock(0, 0, eIBLock_Discard);
	uint16* indices = static_cast<uint16*>(ib_void);

	for (uint index = 0; index < STRING_BUFFER_SIZE; ++index)
	{
		uint16* ib = &indices[index*6];
		ib[0] = index*4 + 0;
		ib[1] = index*4 + 1;
		ib[2] = index*4 + 2;
		ib[3] = index*4 + 2;
		ib[4] = index*4 + 3;
		ib[5] = index*4 + 0;
	}

	m_indices->Unlock();
}

void CFont::LoadGlyps()
{
	DestroyGlyphs();

	for (uint glyph_index = FIRST_GLYPH; glyph_index < GLYPHS_COUNT; ++glyph_index)
		LoadGlyph(glyph_index);

	//	load space glyph anyway
	if (FIRST_GLYPH > SPACE_INDEX)
		LoadGlyph(SPACE_INDEX);
}

void CFont::LoadGlyph(uint glyph_index)
{
	FT_GlyphSlot glyph_slot = m_FTFace->glyph;
	FT_Glyph& glyph = m_glyphs[glyph_index];

	FT_Load_Char(m_FTFace, glyph_index, FT_LOAD_RENDER | FT_LOAD_CROP_BITMAP | FT_LOAD_NO_BITMAP);
	FT_Get_Glyph(glyph_slot, &glyph);

	SGlyphSlot& slot = m_slots[glyph_index];
	slot.bearing.x = glyph_slot->bitmap_left;
	slot.bearing.y = glyph_slot->bitmap_top;
	slot.advance.x = glyph_slot->advance.x >> 6;
	slot.advance.y = glyph_slot->advance.y >> 6;

	FT_BBox bbox;
	FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &bbox);
	slot.bbox = Rect(bbox.xMin, bbox.yMin, bbox.xMax - bbox.xMin, bbox.yMax - bbox.yMin);
}

void CFont::DestroyGlyphs()
{
	for (uint i = FIRST_GLYPH; i < GLYPHS_COUNT; ++i)
		DestroyGlyph(i);
	DestroyGlyph(SPACE_INDEX);
}

void CFont::DestroyGlyph(uint glyph_index)
{
	FT_Glyph& glyph = m_glyphs[glyph_index];
	if (glyph)
	{
		FT_Done_Glyph(glyph);
		glyph = 0;
	}
}

void CFont::GenerateTexture(uint width, uint height)
{
	//	delete old texture
	g_pTextureManager->DeleteTexture(m_texture);

	//	create new one
	m_texture = g_pTextureManager->CreateTexture(width, height, 1, eTexFormat_L8, false);
	ITexture* pTexture = g_pTextureManager->GetTexture(m_texture);
	ITexture::LockedRect data = pTexture->Lock(0, eTexLock_Discard);
	uint8* bits = static_cast<uint8*>(data.bits);

	//	copy glyphs bitmaps to texture
	for (uint glyph_index = FIRST_GLYPH; glyph_index < GLYPHS_COUNT; ++glyph_index)
	{
		FT_Glyph glyph = m_glyphs[glyph_index];
		if (!glyph)	//	ignore unsupported glyphs
			return;

		FT_BitmapGlyph bitmap_glyph = reinterpret_cast<FT_BitmapGlyph>(glyph);
		FT_Bitmap bitmap = bitmap_glyph->bitmap;

		if (!bitmap.buffer)	//	some glyphs don't have bitmap representation (space for example)
			continue;

		//	get pixel mode
		CGlyphBitmap::EPixelMode pixel_mode;
		switch (bitmap.pixel_mode)
		{
		case FT_PIXEL_MODE_MONO:
			pixel_mode = CGlyphBitmap::ePixelMode_1bpp;
			break;

		case FT_PIXEL_MODE_GRAY:
			pixel_mode = CGlyphBitmap::ePixelMode_8bpp;
			break;

		default:
			Error("[CFont] Glyph bitmap has unsupported pixel mode");
		}

		//	create bitmap buffer wrapper
		uint bitmap_width = bitmap.width;
		uint bitmap_height = bitmap.rows;
		CGlyphBitmap bitmap_data(bitmap.buffer, bitmap_width, bitmap_height, bitmap.pitch, pixel_mode);

		//	fill font texture
		SGlyphSlot& slot = m_slots[glyph_index];
		for (uint y = 0; y < bitmap_height; ++y)
		{
			for (uint x = 0; x < bitmap_width; ++x)
			{
				uint offset = (slot.pos[0].x + x) + (slot.pos[0].y + y)*data.pitch;
				assert(offset < width * height);
				bits[offset] = bitmap_data.GetPixel(x, y);
			}
		}

		//	set texture coordinates
		slot.uv[0].x = (slot.pos[0].x - 0.0f)/width;
		slot.uv[0].y = (slot.pos[0].y - 0.0f)/height;
		slot.uv[1].x = (slot.pos[1].x + 0.0f)/width;
		slot.uv[1].y = (slot.pos[1].y + 0.0f)/height;
	}

	pTexture->Unlock(0);
	pTexture->SavePNG("font_cache.png");
}